package decoder;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;

public class AndroidXmlDecoder {

    public static void main(String[] args) {
        System.out.println(byteToInt(new byte[]{0x34, 0x23, 0x00, 0x00}));
        System.out.println(hexSmallEndian(new byte[]{0x00, 0x00, 0x23, 0x34}));
        File f = new File("src/AndroidManifest.xml");
        System.out.println(f.getAbsolutePath());
        decodeBinaryXmlFile(f);
//        System.out.println(new String(new byte[]{0x74,0x68,0x65,0x6d,0x65}));
    }


    /**
     * String Chunk Header:             0x01001c00
     * Resource Chunk Header:           0x00080180
     * Start NameSpace Chunk Header:    0x00100100
     * End NameSpace Chunk Header:      0x00100101
     * Start Tag Chunk Header:          0x00100102
     * End Tag Chunk Header:            0x00100103
     * Text Chunk Header:               0x00100104
     * <p>
     * <p>
     * first meet string chunk:
     * chunk type、chunk size、string count、style Count
     * unknown、stringPoolOffset、stylePoolOffset、stringOffsets、styleOffsets
     *
     * @param f the binary xml file to be decoded
     */
    @SuppressWarnings("all")
    public static void decodeBinaryXmlFile(File f) {
        byte[] fourByteBuf = new byte[4];
        try {
            RandomAccessFile raf = new RandomAccessFile(f, "rw");
            BufferedInputStream bufStream = new BufferedInputStream(new FileInputStream(f));
            //校验魔数
            raf.read(fourByteBuf);
            if (Arrays.equals(fourByteBuf, new byte[]{0x03, 0x00, 0x08, 0x00})) {
                //文件大小
                raf.read(fourByteBuf);
                System.out.println("binary file size: " + byteToInt(fourByteBuf) + "字节");
                //解析chunk
                int stringChunkSize, stringCount, stringPoolOffset;
                raf.read(fourByteBuf);//String Chunk Type?
                if (Arrays.equals(fourByteBuf, new byte[]{0x01, 0x00, 0x1c, 0x00})) {
                    //String Chunk
                    System.out.println("=============String Chunk=============");
                    raf.read(fourByteBuf);//chunk size
                    stringChunkSize = byteToInt(fourByteBuf);
                    raf.read(fourByteBuf);//string count
                    stringCount = byteToInt(fourByteBuf);
                    raf.read(fourByteBuf);//style Count
                    raf.read(fourByteBuf);//unknown
                    raf.read(fourByteBuf);//stringPoolOffset
                    stringPoolOffset = byteToInt(fourByteBuf);
                    raf.read(fourByteBuf);//stylePoolOffset
                    //直接跳到string开始的位置
                    raf.skipBytes((int) (stringPoolOffset - 7 * 4));//前面从chunkType开始已经读取了7个字段（7*4byte)）

                    byte[] stringLenBuf = new byte[2];
                    int stringLen;
                    ArrayList<String> stringCollec = new ArrayList<>();
                    while (stringCollec.size() < stringCount) {
                        raf.read(stringLenBuf);
                        stringLen = (short) byte2Short(stringLenBuf);
                        System.out.print("string len:" + stringLen + " \t");
                        byte[] bs = new byte[stringLen];
                        for (int i = 0; i < stringLen * 2; i++) {

                            int read = raf.read();
                            if (read != 0x00)
                                bs[i / 2] = (byte) read;
                        }
                        raf.skipBytes(2);//字符串结束符号 0x0000
                        String s = new String(bs);
                        stringCollec.add(s);
                        System.out.println(s);

                    }

                    System.out.println("71: ----------------------" + stringCollec.get(71));
                    System.out.println("=============String Chunk End=============");

                    raf.seek(8 + stringChunkSize);
                    raf.read(fourByteBuf);
                    int resourceChunkSize = 0, resourceIdCount;
                    ArrayList<Integer> resourceIds = new ArrayList<>();
                    if (Arrays.equals(fourByteBuf, new byte[]{(byte) 0x80, 0x01, 0x08, 0x00})) {
                        System.out.println("=============Resource Chunk Start=============");
                        raf.read(fourByteBuf);
                        resourceChunkSize = byteToInt(fourByteBuf);
                        resourceIdCount = resourceChunkSize / 4 - 2;
                        for (int i = 0; i < resourceIdCount; i++) {
                            raf.read(fourByteBuf);
                            resourceIds.add(byteToInt(fourByteBuf));
                        }

                        System.out.println("=============Resource Chunk End=============");
                        raf.seek(8 + stringChunkSize + resourceChunkSize);
                    }

                    raf.read(fourByteBuf);
                    int nameSpaceChunkSize = 0, nameSpaceLineNumber, nameSpaceCount;
                    int prefix = 0, uri = 0;
                    if (Arrays.equals(fourByteBuf, new byte[]{0x00, 0x01, 0x10, 0x00})) {
                        System.out.println("=============NameSpace Chunk Start=============");
                        raf.read(fourByteBuf);
                        nameSpaceChunkSize = byteToInt(fourByteBuf);
                        raf.read(fourByteBuf);//lineNumber
                        nameSpaceLineNumber = byteToInt(fourByteBuf);
                        raf.skipBytes(4);//0xffffffff  unknown
                        raf.read(fourByteBuf);//prefix
                        prefix = byteToInt(fourByteBuf);
                        raf.read(fourByteBuf);//uri
                        uri = byteToInt(fourByteBuf);
                        System.out.println("uri: " + stringCollec.get(uri));
                        System.out.println("prefix: " + stringCollec.get(prefix));
                        System.out.println("=============NameSpace Chunk End=============");
                    }


                    //forward to start tag chunk
                    raf.seek(8 + stringChunkSize + resourceChunkSize + nameSpaceChunkSize);
                    raf.read(fourByteBuf);//startTag
                    int attributeCount = 0, nameIndex = 0;
                    if (Arrays.equals(fourByteBuf, new byte[]{0x02, 0x01, 0x10, 0x00})) {
                        System.out.println("=========StartTag Chunk Start============");

                        raf.read(fourByteBuf);//chunkSize
                        raf.read(fourByteBuf);//lineNumber
                        raf.read(fourByteBuf);//unknonwn or prefix if not equal -1
                        raf.read(fourByteBuf);//NamseSpace Uri
                        raf.read(fourByteBuf);//nameIndex
                        raf.read(fourByteBuf);//flags ————denote a start tag or a end tag
                        raf.read(fourByteBuf);//attribute count
                        attributeCount = byteToInt(fourByteBuf);
                        raf.read(fourByteBuf);//classAttribute

                        ArrayList<Attribute> attributes = new ArrayList<>();
                        //[namespace,uri,nameIndex,valueString,data]
                        for (int i = 0; i < attributeCount; i++) {
                            Attribute attribute = new Attribute();
                            for (int j = 0; j < 5; j++) {
                                raf.read(fourByteBuf);
                                switch (j) {
                                    case 0:
                                        attribute.nameSpace = byteToInt(fourByteBuf);
                                        break;
                                    case 1:
                                        attribute.name = byteToInt(fourByteBuf);
                                        break;
                                    case 2:
                                        attribute.uri = byteToInt(fourByteBuf);
                                        break;
                                    case 3:
                                        attribute.valueString = byteToInt(fourByteBuf) >> 24;
                                        break;
                                    case 4:
                                        attribute.data = byteToInt(fourByteBuf);
                                        break;
                                }
                                attributes.add(attribute);
                            }
                            System.out.println(attribute.toString(stringCollec));
                        }


//                        printAttribute(stringCollec, uri, nameIndex, attributes);
                        System.out.println("=========StartTag Chunk End============");
                    }

                }


            }
            raf.close();
        } catch (FileNotFoundException e) {
            System.out.println("binary xml file not exists");
        } catch (IOException e) {
        }

    }

    private static void printAttribute(ArrayList<String> stringCollec, int uriIndex, int nameIndex, ArrayList<Attribute> attributes) {

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append('<');
        stringBuilder.append(stringCollec.get(nameIndex));
        stringBuilder.append('\n');
        for (int i = 0; i < attributes.size(); i++) {
            Attribute attribute = attributes.get(i);
            stringBuilder.append(stringCollec.get(attribute.uri));
            stringBuilder.append(':');
            stringBuilder.append(stringCollec.get(attribute.name));
            stringBuilder.append('=');
            stringBuilder.append('"');
            stringBuilder.append(stringCollec.get(attribute.valueString));
            System.out.println("data::::::::::::" + stringCollec.get(attribute.data));
            stringBuilder.append('"');
            if (i != attributes.size()) ;
            stringBuilder.append('\n');
        }
        stringBuilder.append('>');
        System.out.println(stringBuilder.toString());
    }


    /**
     * 十六进制大端转十进制
     * {0x21,0x32,0x00,0x00}
     *
     * @param hexNumber ..
     */
    public static int byteToInt(byte[] hexNumber) {
        return (hexNumber[0] & 0xff) | ((hexNumber[1] << 8) & 0xff00)
                | ((hexNumber[2] << 24) >>> 8) | (hexNumber[3] << 24);

    }

    public static short byte2Short(byte[] b) {
        short s = 0;
        short s0 = (short) (b[0] & 0xff);
        short s1 = (short) (b[1] & 0xff);
        s1 <<= 8;
        s = (short) (s0 | s1);
        return s;
    }

    public static long hexSmallEndian(byte[] hexNumber) {
        int len = hexNumber.length;
        long sum = 0;
        for (int i = 0; i < len; i++) {
            byte n = hexNumber[i];
            sum += n << (8 * (len - i - 1));
        }
        return sum;
    }

    static class Attribute {
        public int nameSpace;
        public int name;
        public int uri;
        public int valueString;
        public int data;

        public Attribute() {

        }


        public String toString(ArrayList<String> sCollec) {
            String ns = nameSpace == -1 ? "" : sCollec.get(nameSpace);
            String uriStr = uri == -1 ? "" : sCollec.get(uri);
            String nameStr = name == -1 ? "" : sCollec.get(name);
            String valueStr = valueString == -1 ? "" : sCollec.get(valueString);
            String dataStr = data == -1 ? "" : sCollec.get(data);


            return "nameSpace: " + ns + "\t uri: " + uriStr + " \t name: " + nameStr + " \t valueString: " + valueString + " \t data: " + dataStr;
        }
    }
}
